(** Actual Code Generation *)

open Symbols;;
open Environment;;

(** output the string with a newline on the end *)
let gen ch code = output_string ch (code ^ "\n");;

let rec gen_stmt ch s = match s with
  | Assign (e1, e2) -> gen_expr ch e2;
        gen_assign ch e1;
  | Stmt_list ss -> List.iter (gen_stmt ch) ss
  | Apodef (id, apocode) -> gen ch "{";
        gen ch apocode;
        gen ch "}";
        gen_assign ch (Ident id;)
  | Return e -> gen_expr ch e; gen ch "return"
  | Funcdef (id, args, body) ->
        gen ch "{";
        List.iter (fun a -> gen_assign ch (Ident a)) args;
        List.iter (gen_stmt ch) body;
        gen_stmt ch (Return Noneval);  (*At the end of every function body return None to mantain the invariant that a function always returns exactly one item on the stack*)
        gen ch "}";
        gen ch ("\"" ^ id ^ "\"");
        gen ch "store"
  | Expr_stmt e ->
        gen_expr ch e;
        gen ch "discard"
  | Pass -> ()
  | If_stmt (test, if_t, if_f) -> 
        gen ch "{";
        List.iter (gen_stmt ch) if_t;
        gen ch "}";
        gen ch "{";
        List.iter (gen_stmt ch) if_f;
        gen ch "}";
        gen ch "{";
        gen_expr ch test;
        gen ch "}";
        gen ch "branch";
  | Classdef (id, parents, body) ->
        gen ch "{";
        List.iter (gen_stmt ch) body;
        gen_expr ch parents;
        gen_assign ch (Ident "__parent__");
        gen ch "push_env";
        gen ch "}";
        gen ch "call";
        gen_assign ch (Ident id)
  | While_stmt (test, body) ->
        gen ch "{";
        List.iter (gen_stmt ch) body;
        gen ch "}";
        gen ch "{";
        gen_expr ch test;
        gen ch "}";
        gen ch "loop"
  | For_stmt (var, iter, body) ->
        gen ch "{";
        gen_expr ch iter;
        gen_assign ch (Ident "__iter");
        gen ch "{";
        gen_stmt ch (Assign (var, Call(Attr_ref(Ident "__iter", "next"), [])));
        List.iter (gen_stmt ch) body;
        gen ch "}";
        gen ch "{";
        gen_expr ch (Call(Attr_ref(Ident "__iter", "hasNext"),[]));
        gen ch "}";
        gen ch "loop";
        gen ch "}";
        gen ch "call"

and gen_expr ch e = match e with
    Ident id -> gen ch ("\"" ^ id ^ "\"");
        gen ch "load"
  | Integer i -> gen ch (string_of_int i);
        gen_expr ch (Call(Ident "new", [Ident "int"]))
  | Boolean b -> if b then gen ch "TRUE" else gen ch "FALSE"
  | String s -> gen ch ("\"" ^ s ^ "\"");
          gen_expr ch (Call(Ident "new", [Ident "string"]))
  | Call (callable, args) ->
        List.iter (gen_expr ch) (List.rev args); 
        gen_expr ch callable;
        gen ch "call"
  | Noneval -> gen ch "none"
  | Monop (op, obj) -> gen_expr ch (Call(Attr_ref(obj, "__" ^ op ^ "__"), []))
  | Binop (op, obj, obj') -> gen_expr ch (Call(Attr_ref(obj, "__" ^ op ^ "__"), [obj']))
  | Attr_ref (obj, attr) -> 
        gen ch ("\"" ^ attr ^ "\"");
        gen_expr ch obj;
        gen ch ("\"__getattribute__\"");
        gen ch "load call"
  | Dict_display pairs -> List.iter (fun (x,y) -> gen_expr ch y; gen_expr ch x) pairs;
        gen ch (string_of_int (List.length pairs));
        gen ch "new_dict"
  | Lambda_expr (args, sub_e) ->
        gen ch "{";
        List.iter (fun a -> gen_assign ch (Ident a)) args;
        gen_expr ch sub_e;
        gen ch "}"

and gen_assign ch e = match e with
    Ident id -> gen ch ("\"" ^ id ^ "\"");
        gen ch "store"
  | Attr_ref (obj, attr) ->
        gen ch ("\"" ^ attr ^ "\"");
        gen_expr ch (Call(Ident("__setattribute__"), [obj]))
;;